/*
 * Copyright (C) 2018 The Asiainfo-Ability Authors
 *
 *      Licensed under the Apache License, Version 2.0 (the "License");
 *      you may not use this file except in compliance with the License.
 *      You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *      Unless required by applicable law or agreed to in writing, software
 *      distributed under the License is distributed on an "AS IS" BASIS,
 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *      See the License for the specific language governing permissions and
 *      limitations under the License.
 */

package org.asiainfo.ability.base.utils;


import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Author: visen
 * @Date: 2018/1/27
 * @Description: 简单生成19位的有序编码序列 乐观锁 支持高并发
 */
@SuppressWarnings("unchecked")
public class SeqUtils implements Sequence {


    private SID sid;

    public static Sequence createCluster() {
        return new SeqCluster();
    }

    public static Sequence newInstance(int shardingId) {
        return new SeqUtils(shardingId);
    }

    public static Sequence newInstance() {
        return new SeqUtils(0);
    }

    public static class SeqCluster implements Sequence {
        private SeqUtils[] seqUtilsArr;
        private int clouterSize;

        public SeqCluster() {
            this(1);
        }

        public SeqCluster(int clusterSize) {
            if (clusterSize > 64 || clusterSize < 1) {
                throw new IllegalArgumentException("clusterSize must between 1 - 64");
            }
            this.clouterSize = clusterSize;
            seqUtilsArr = new SeqUtils[clusterSize];
            for (int i = 0; i < clusterSize; i++) {
                seqUtilsArr[i] = new SeqUtils(i);
            }
        }

        public long id() {
            return seqUtilsArr[ThreadLocalRandom.current().nextInt(clouterSize)].id();
        }

        public String idStr() {
            return seqUtilsArr[ThreadLocalRandom.current().nextInt(clouterSize)].idStr();
        }


    }

    private SeqUtils(int shardingId) {
        sid = new SID(shardingId);
    }


    //concurrent obatin 65536 per second
    //6 bit sharding id
    public long id() {
        return sid.nextId();
    }

    public String idStr() {
        long id = sid.nextId() >> 22;
        id = id + 1000000000000L;
        long seq = id & 65535;
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-");
        return sf.format(new Date(id)) + seq;
    }

    //random length，may be duplicate，need checking
    public static String tokens(int length) {
        char[] buf = new char[length];
        for (int i = 0; i < length; i++) {
            buf[i] = CHARS[(int) Math.floor(Math.random() * RADIX)];
        }
        return String.valueOf(buf);
    }

    private final static char[] CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    private final static int RADIX = CHARS.length;


    class SID {

        // 2001
        private final static long base = 1000000000000L;
        //private final static long base = 1000000000000L;

        private final static long shardingIdBits = 6L;

        private final static long sequenceBits = 16L;

        //private final static long shardingIdShift = sequenceBits;

        private final static long dataTypeShift = sequenceBits + shardingIdBits;

        private final static long timestampLeftShift = dataTypeShift;

        // upper value
        private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
        private final static long workerIdUpper = -1L ^ (-1L << shardingIdBits);
        private final static long maxCycle = sequenceMask;


        private long lastTime = 0L;
        private volatile  long sequence = 0L;
        private AtomicInteger atomicLong;
        private long shardingId;
        private ReentrantReadWriteLock lock;

        public SID(long shardingId) {
            if (shardingId > 63 || shardingId < 0) {
                throw new IllegalArgumentException("sharding id must between 0 - 63");
            }
            this.shardingId = shardingId;
            atomicLong = new AtomicInteger(0);
            lock = new ReentrantReadWriteLock();
        }

        //gen 65535 per second
        public long nextId() {

            long curValue = 0;
            try {
                lock.readLock().lock();
                curValue = atomicLong.getAndAdd(1);
            } catch (Exception e) {
            } finally {
                lock.readLock().unlock();
            }


            if (curValue > maxCycle) {
                try {
                    lock.writeLock().lock();
                    long preValue = atomicLong.getAndAdd(1);
                    if (preValue > curValue) {
                        preValue = preValue % 65535;
                        atomicLong.set((int) preValue + 1);
                    }
                    curValue = preValue;

                } finally {
                    lock.writeLock().unlock();
                }
            }
            return ((System.currentTimeMillis() - base) << timestampLeftShift)
                    | (shardingId << sequenceBits) | (curValue % 65535);

        }

        public SID() {
            this(0);
        }


    }


    private final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    //native test
    public static void main(String[] args) {
        System.out.println((65537 & 65535));
        final Sequence sequence = SeqUtils.createCluster();
        //final Sequence sequence = new SeqUtils(1);
        long id = sequence.id() >> 22;
        Long l;
        id = id + 1000000000000L;
        SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
        System.out.println(sf.format(new Date(System.currentTimeMillis())));
        sf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
        SeqCluster seqCluster = new SeqCluster(64);
        for (int i = 0; i < 100; i++) {
           // System.out.println(seqUtils.idStr());
        }


        final ConcurrentHashMap map1 = new ConcurrentHashMap();

        long old = System.currentTimeMillis();
        for (int i = 0; i < 800000; i++) {
           map1.put(sequence.id(), "");

        }
        System.out.println(map1.size() + "====111===" + (System.currentTimeMillis() - old));
        final ConcurrentHashMap map = new ConcurrentHashMap();
        final CountDownLatch c = new CountDownLatch(10000);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1000, 1000, 600L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(505536));
        for (int i = 0; i < 10000; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {

                    for(int i = 0; i < 100; i++) {
                        map.put(sequence.id(), "");
                    }
                    c.countDown();
                }

            });

        }
        try {
            c.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(map.size() + "=======" + (System.currentTimeMillis() - old));
        executor.shutdown();
    }

}
